package org.cy.evolution.ioc.compiler.generator;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import org.cy.evolution.ioc.compiler.constant.EvolutionConstant;
import org.cy.evolution.ioc.compiler.utils.ClassUtil;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

public class AutoInjectGenerator {

    /**
     * 日志工具类
     */
    private Messager messager;

    /**
     * 包名
     */
    private String packageName;

    /**
     * 被注解修饰字段的所属类元素
     */
    private TypeElement classElement;

    /**
     * 代理类名
     */
    private String proxyClassName;

    /**
     * 保存所有的注解信息
     */
    private List<Element> annotationVariables = new ArrayList<>(0);

    public void add(Element element) {
        annotationVariables.add(element);
    }

    public AutoInjectGenerator(final Elements elements, TypeElement classElement, Messager messager) {
        this.messager = messager;
        String packageName = ClassUtil.getPackageName(elements, classElement);
        this.packageName = packageName;
        this.classElement = classElement;
        String className = ClassUtil.getClassName(classElement, packageName);
        this.proxyClassName = className + EvolutionConstant.INJECTOR_GENERATE_SUFFIX;
    }

    /**
     * 生成Java源码文件对象
     * @return 源码对象
     */
    public JavaFile generate() {

        TypeSpec injectorClass = this.generateInjectorClass(
                this.generateInjectMethod(),
                this.generateDestroyMethod(),
                this.generateBuilderClass(
                        this.generateBuildMethod()
                )
        );

        return JavaFile.builder(this.packageName, injectorClass).build();
    }

    /**
     * 生成方法: build
     * public EvolutionInjector<T> build() {}
     * @return 方法对象
     */
    private MethodSpec generateBuildMethod() {
        return MethodSpec.methodBuilder("build")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .returns(ParameterizedTypeName.get(EvolutionConstant.INTERFACE_INJECTOR, TypeName.get(classElement.asType())))
                .addStatement("return new $T()", ClassName.get(this.packageName, this.proxyClassName))
                .build();
    }

    /**
     * 生成内部类: Builder
     * public static class Builder extends EvolutionInjector.Builder<T> {}
     * @param buildMethod 实现的抽象方法: build
     * @return 内部类对象
     */
    private TypeSpec generateBuilderClass(MethodSpec buildMethod) {
        return TypeSpec.classBuilder("Builder")
                .addModifiers(Modifier.PUBLIC)
                .addModifiers(Modifier.STATIC)
                .superclass(ParameterizedTypeName.get(EvolutionConstant.ABSTRACT_INJECTOR_BUILDER,
                        ParameterizedTypeName.get(EvolutionConstant.INTERFACE_INJECTOR, TypeName.get(classElement.asType()))))
                .addMethod(buildMethod)
                .build();
    }

    /**
     * 生成方法： Inject
     * public void inject(T instance) {}
     * @return 方法对象
     */
    private MethodSpec generateInjectMethod() {
        MethodSpec.Builder injectMethod = MethodSpec.methodBuilder("inject")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .returns(void.class)
                .addParameter(TypeName.get(classElement.asType()), "instance");
        injectMethod.addStatement("this.instance = instance");
        for (Element element : annotationVariables) {
            VariableElement variableElement = (VariableElement) element;

            if (variableElement.getModifiers().contains(Modifier.PRIVATE)) {
                this.messager.printMessage(Diagnostic.Kind.ERROR, String.format("%s field must not be private！", variableElement.getSimpleName()));
            } else {
                injectMethod.addStatement("this.instance.$L = $T.get($T.COMPONENT).get($T.class.getCanonicalName())", variableElement.toString(),
                        EvolutionConstant.EVOLUTION_CONTEXT, EvolutionConstant.EVOLUTION_CONTEXT,
                        ClassName.get(variableElement.asType()));
            }
        }
        return injectMethod.build();
    }

    /**
     * 生成方法： Destroy
     * public void destroy(T instance) {}
     * @return 方法对象
     */
    private MethodSpec generateDestroyMethod() {
        MethodSpec.Builder destroyMethod = MethodSpec.methodBuilder("destroy")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .returns(void.class);
        destroyMethod.beginControlFlow("if (null != this.instance)");
        for (Element element : annotationVariables) {
            VariableElement variableElement = (VariableElement) element;
            destroyMethod.addStatement("this.instance.$L = null", variableElement.toString());
        }
        destroyMethod.endControlFlow();
        return destroyMethod.build();
    }

    /**
     * 生成注入类： XXXInjector
     * public class XXXInjector implements EvolutionInjector<XXX> {}
     * @param inject 方法： inject
     * @param destroy 方法： destroy
     * @param innerClass 内部类： Builder
     * @return 注入类对象
     */
    private TypeSpec generateInjectorClass(MethodSpec inject, MethodSpec destroy, TypeSpec innerClass) {
        FieldSpec instance = FieldSpec.builder(TypeName.get(classElement.asType()), "instance", Modifier.PRIVATE).build();

        return TypeSpec.classBuilder(proxyClassName)
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ParameterizedTypeName.get(EvolutionConstant.INTERFACE_INJECTOR, TypeName.get(classElement.asType())))
                .addField(instance)
                .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
                .addMethod(inject)
                .addMethod(destroy)
                .addType(innerClass)
                .build();
    }
}
